package com.terrace.core.mybatis.plugin.pagination.parser;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.session.Configuration;

import com.terrace.core.mybatis.plugin.pagination.Dialect;
import com.terrace.core.mybatis.plugin.pagination.sqlsource.CustomProviderSqlSource;
import com.terrace.core.mybatis.entity.Pagination;

/**
 * 抽象的SQL解析器
 * @author jiangyg
 *
 */
public abstract class AbstractParser implements Parser {
	
	/**
	 * 实例化
	 * @param dialect
	 * @return
	 */
	public static Parser instanceParser(Dialect dialect) {
		Parser parser = null;
		switch (dialect) {
			case mysql:
			case mariadb:
				parser = new MysqlParser();
				break;
			case oracle:
				parser = new OracleParser();
				break;
			default:
				throw new RuntimeException("分页插件" + dialect + "方言错误!");
		}
		return parser;
	}
	
	/**
	 * 获取分页统计SQL
	 * 
	 * @param sql
	 *            原SQL
	 * @return
	 */
	@Override
	public String getCountSQL(String sql) {
		return "SELECT COUNT(1) FROM (" + sql + ") _SELECT_PAGINATION_COUNT";
	}

	/**
	 * 获取分页SQl
	 * 
	 * @param sql
	 *            原SQL
	 * @return
	 */
	@Override
	public abstract String getPaginationSQL(String sql);

	/**
	 * 获取分页参数映射
	 *
	 * @param configuration
	 * @param boundSql
	 * @return
	 */
	public List<ParameterMapping> getPageParameterMapping(Configuration configuration, BoundSql boundSql) {
		List<ParameterMapping> newParameterMappings = new ArrayList<ParameterMapping>();
		if (boundSql != null && boundSql.getParameterMappings() != null) {
			newParameterMappings.addAll(boundSql.getParameterMappings());
		}
		newParameterMappings.add(new ParameterMapping.Builder(configuration, PAGEPARAMETER_FIRST, Integer.class).build());
		newParameterMappings.add(new ParameterMapping.Builder(configuration, PAGEPARAMETER_SECOND, Integer.class).build());
		return newParameterMappings;
	}

	/**
	 * 设置分页参数
	 *
	 * @param ms
	 * @param parameterObject
	 * @param boundSql
	 * @param page
	 * @return
	 */
	public Map<String, Object> setPageParameter(MappedStatement ms, Object parameterObject, BoundSql boundSql, Pagination<?> pagination) {
		return processParameter(ms, parameterObject, boundSql);
	}

	@SuppressWarnings("unchecked")
	public static Map<String, Object> processParameter(MappedStatement ms, Object parameterObject, BoundSql boundSql) {
        Map<String, Object> paramMap = null;
        // 没有传入参数
        if (parameterObject == null) {
            paramMap = new HashMap<String, Object>();
        } 
        // 传入map形式的参数
        else if (parameterObject instanceof Map) {
            //解决不可变Map的情况
            paramMap = new HashMap<String, Object>();
            paramMap.putAll((Map<String, Object>) parameterObject);
        } 
        // 传入实体类参数
        else {
            paramMap = new HashMap<String, Object>();
            //动态sql时的判断条件不会出现在ParameterMapping中，但是必须有，所以这里需要收集所有的getter属性
            //TypeHandlerRegistry可以直接处理的会作为一个直接使用的对象进行处理
            boolean hasTypeHandler = ms.getConfiguration().getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass());
            MetaObject metaObject = SystemMetaObject.forObject(parameterObject);
            //需要针对注解形式的MyProviderSqlSource保存原值
            if (ms.getSqlSource() instanceof CustomProviderSqlSource) {
                paramMap.put(PROVIDER_OBJECT, parameterObject);
            }
            if (!hasTypeHandler) {
                for (String name : metaObject.getGetterNames()) {
                    paramMap.put(name, metaObject.getValue(name));
                }
            }
            //下面这段方法，主要解决一个常见类型的参数时的问题
            if (boundSql.getParameterMappings() != null && boundSql.getParameterMappings().size() > 0) {
                for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
                    String name = parameterMapping.getProperty();
                    if (!name.equals(PAGEPARAMETER_FIRST) && !name.equals(PAGEPARAMETER_SECOND) && paramMap.get(name) == null) {
                        if (hasTypeHandler || parameterMapping.getJavaType().equals(parameterObject.getClass())) {
                            paramMap.put(name, parameterObject);
                            break;
                        }
                    }
                }
            }
        }
        //备份原始参数对象
        paramMap.put(ORIGINAL_PARAMETER_OBJECT, parameterObject);
        return paramMap;
    }

}
